#include "userprog/syscall.h"
#include "userprog/process.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/vaddr.h"
#include "threads/synch.h"
#include "devices/input.h"
#include <filesys/filesys.h>
#include <filesys/file.h>

static void syscall_handler (struct intr_frame *);
void getTheArgv(struct intr_frame *f, int *argv, int argc);
void check_addr(const void* vaddr, uint8_t argc);
static int get_user (const uint8_t *uaddr);
struct OpenFilesOfThread * getFileByFd (int fd);

struct lock file_lock;

void acquire_file_lock(){
    lock_acquire(&file_lock);
}

void release_file_lock(){
    lock_release(&file_lock);
}

void syscall_init (void) 
{
  lock_init(&file_lock);//init the file lock
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}
static void
syscall_handler (struct intr_frame *f UNUSED) 
{
  // printf ("system call!\n");
  // thread_exit ();   
  // get which syscall to be called
  int syscall = *((int *)f->esp);
  check_addr((uint32_t *)(f->esp),4);
  int argv[4];
  switch (syscall)
  {
  case SYS_HALT:
    shutdown_power_off();
    break;
  case SYS_EXIT:
    getTheArgv(f, argv, 2);
    check_addr(f->esp,4);
    exit(argv[1]);
    break;
  case SYS_WRITE:
    getTheArgv(f, argv, 4);
    f->eax = write((int)argv[1], (const void *)argv[2], (unsigned)argv[3]);
    break;
  case SYS_OPEN:
    getTheArgv(f, argv, 2);
    f->eax = open((const char *)argv[1]);
    break;
  case SYS_EXEC:
    getTheArgv(f, argv, 2);
    f->eax = exec((const char *)argv[1]);
    // printf("%d", f->eax);
    break;
  case SYS_WAIT:
    getTheArgv(f, argv, 2);
    f->eax = wait((tid_t) argv[1]);
    break;
  case SYS_CLOSE:
    getTheArgv(f, argv, 2);
    close((int) argv[1]);
    break;
  case SYS_CREATE:
    getTheArgv(f, argv, 3);
    f->eax = create((const char *)argv[1], argv[2]);
    break;
  case SYS_REMOVE:
    getTheArgv(f, argv, 2);
    f->eax =remove((const char*) argv[1]);
    break;
  case SYS_FILESIZE:
    getTheArgv(f, argv, 2);
    f->eax = filesize((int)argv[1]);
    break;
  case SYS_READ:
    getTheArgv(f, argv, 4);
    f->eax = read((int)argv[1], (const void *)argv[2],(unsigned)argv[3]);
    break;
  case SYS_SEEK:
    getTheArgv(f, argv, 3);
    seek((int)argv[1], (unsigned int)argv[2]);
    break;
  case SYS_TELL:
    getTheArgv(f, argv, 2);
    f->eax = tell((int)argv[1]);
    break;
  default:
    exit(-1);
    break;
  }
}


void exit(int exit_code){
  struct thread * cur = thread_current();
  cur->exit_code = exit_code;
  thread_exit();
}

pid_t exec (const char *file){
  check_addr(file, 4);
  return process_execute(file);
}
int wait (tid_t pid){
  return process_wait(pid);
}
int write(int fd, const void *buffer, unsigned length){
    check_addr(buffer,1);
    check_addr(buffer+length-1,1);
    int ret_num=0;
    buffer = (char *)buffer;
    if (fd == 1){
      putbuf (buffer, length);
    }else{
      struct OpenFilesOfThread * file = getFileByFd(fd);
      if (file == NULL){
        return 0;
      }
      acquire_file_lock();
      length = file_write(file->file, buffer, length);
      release_file_lock();
    }
    return length;
}
void seek (int fd, unsigned position){
  struct OpenFilesOfThread * file = getFileByFd(fd);
  if(file != NULL){
    acquire_file_lock();
    file_seek(file->file, position);
    release_file_lock();
  }
}
unsigned tell (int fd){
  unsigned ret;
  struct OpenFilesOfThread * file = getFileByFd(fd);
  if(file == NULL){
    return -1;
  }
  acquire_file_lock();
  ret = file_tell(file->file);
  release_file_lock();
  return ret;
}


int open(const char *file){
  check_addr(file, 1);
  // printf("%s", file);
  acquire_file_lock();
  struct file * openFile = filesys_open(file);
  release_file_lock();
  if(openFile == NULL){
    // printf("i cannot coptn");
    return -1;
  }
  struct thread * cur_t = thread_current();
  struct OpenFilesOfThread *OpenFileOfThread = malloc(sizeof(struct OpenFilesOfThread));
  OpenFileOfThread->fd = cur_t->curMaxFd++;
  OpenFileOfThread->file = openFile;
  list_push_back (&cur_t->openedFiles, &OpenFileOfThread->file_elem);
  return OpenFileOfThread->fd;
}

void close (int fd)
{ 
  struct OpenFilesOfThread * opened_file = getFileByFd (fd);
  if (opened_file != NULL)
  { 
    acquire_file_lock();
    file_close (opened_file->file);
    release_file_lock();
    /* Remove the opened file from the list */
    list_remove (&opened_file->file_elem);
    /* Free opened files */
    free (opened_file);
  }
}

bool create (const char *file, unsigned initial_size)
{
    check_addr(file,14);
    acquire_file_lock();
    //api
    bool ret=filesys_create(file, initial_size);
    release_file_lock();
    return ret;
}

bool remove (const char *file)
{
    check_addr(file,4);
    acquire_file_lock();
    //api
    bool ret=filesys_remove(file);
    release_file_lock();
    return ret;
}

int filesize (int fd)
{
    int ret=-1;
    struct OpenFilesOfThread * opened_file = getFileByFd (fd);
    if(opened_file){
        acquire_file_lock();
        ret=file_length(opened_file->file);
        release_file_lock();
    }
    return ret;
}

int read (int fd, void *buffer, unsigned length)
{
    //buffer check
    check_addr(buffer,1);
    check_addr(buffer+length-1,1);
    int ret;
//    printf("checkpoint\n\n\n");
    if(fd==0){
        //stdin
        uint8_t *buffer=buffer;
        for(int i=0;i<length;i++){
            buffer[i]=input_getc();
        }
        ret=length;
    }
    else{
        struct OpenFilesOfThread * opened_file = getFileByFd (fd);
        if(opened_file!=NULL){
//            printf("arrive here\n\n\n");
            acquire_file_lock();
            ret=file_read(opened_file->file,buffer,(off_t)length);
            release_file_lock();
        }
    }
    return ret;
}

struct OpenFilesOfThread * 
getFileByFd (int fd)
{
  struct list_elem *e;
  struct OpenFilesOfThread * OpenFileOfThread = NULL;
  struct list *openedFiles = &thread_current ()->openedFiles;
  for (e = list_begin (openedFiles); e != list_end (openedFiles); e = list_next (e)){
    OpenFileOfThread = list_entry(e, struct OpenFilesOfThread, file_elem);
    if (fd == OpenFileOfThread->fd)
      return OpenFileOfThread;
  }
  return NULL;
}

//check whether pointer's address is legal
void check_addr(const void* vaddr, uint8_t check_byte){
  for(uint8_t i=0;i<check_byte;i++){
      if(get_user(((uint8_t *)vaddr)+i)==-1){
          exit(-1);
      }
  }
}

static int
get_user (const uint8_t *uaddr)
{
    if(!is_user_vaddr(uaddr)){
        return -1;
    }
  int result;
  asm ("movl $1f, %0; movzbl %1, %0; 1:"
       : "=&a" (result) : "m" (*uaddr));
  return result;
}

void getTheArgv(struct intr_frame *f, int *argv, int argc){
  int i =0;
  int * ptr;
  for (i=0;i<argc;i++){
    ptr=((int *)(f->esp)) + i;
    check_addr(ptr,4);
    argv[i]=*ptr;
  }
}
